/* 
    sample code for JPEG for 7113 composite input and JPEG output
    This sample code is to do encode five JPEG files named "/tmp/img0.jpg"...
 */
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/timeb.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/socket.h>
#include <sys/resource.h>
#include <sys/time.h>
#include <sys/types.h>
#include <linux/videodev.h>
#include <errno.h>
#include <linux/fb.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/un.h>
#include <signal.h>
#include "fmjpeg_avcodec.h"


#define NEW_VERSION
#define FMJPEG_ENCODER_DEV  "/dev/mjenc" //major:10 minior:61
#define devicename "/dev/video0"

typedef struct AVFrame {
    uint8_t *data[4];
} AVFrame;

typedef struct video_profile
{
    unsigned int bit_rate;
    unsigned int width;   //length per dma buffer
    unsigned int height;
    unsigned int framerate;
    unsigned int frame_rate_base;
    unsigned int gop_size;
    unsigned int qmax;
    unsigned int qmin;   
    unsigned int quant;
    unsigned int fmjpeg_image_quality;
    unsigned int u32RestartInterval;
    unsigned int fmjpeg_jpeg;
    unsigned int fmjpeg_yuv_format;
    unsigned int ysize;		
    unsigned int YUV_2D;	
    int roi_enable;
    unsigned int roi_left_x;
    unsigned int roi_left_y;
    unsigned int roi_right_x;
    unsigned int roi_right_y;	
    AVFrame *coded_frame;
} video_profile;

video_profile           video_setting;
int fmjpeg_enc_fd=0;
int ingrab=14;
int cap_type = 0;
FJPEG_ENC_PARAM enc_param;

/*  
#################################################
    capture 
################################################# 
 */
typedef struct fcap_frame_buff
{
    unsigned int phyAddr;
    unsigned int mmapAddr;   //length per dma buffer
    unsigned int frame_no;
}fcap_frame_buff_t;
int video_fd;
int frame_num;
struct video_mbuf       gb_buffers;
struct video_mmap       gb_buf;
struct video_capability video_cap;

unsigned char           *video_buf_virt;
unsigned char           *out_virt_buffer[5];
unsigned char           mmap_addr;
unsigned int            mmap_done=0;
unsigned char        yuv_format;

int v4l_init(int width, int height)
{
    int    cap_type;
    struct video_window win;
    struct video_tuner tuner;

	video_fd = open(devicename, O_RDWR);

	  if(video_fd < 0)
    {
        printf("open device %s failure", devicename);
        return -1;
    }
    
    if (ioctl(video_fd, VIDIOCGCAP, &video_cap) < 0) 	/// query video capture capability
    {
        printf("VIDIOCGCAP failure");
        return -1;
    }

    if (!(video_cap.type & VID_TYPE_CAPTURE)) 
    {
        printf("Fatal: grab device does not handle capture\n");
        return -1;
    }

    if (!ioctl(video_fd, VIDIOCGTUNER, &tuner))
    {
        tuner.mode = VIDEO_MODE_NTSC;
        ioctl(video_fd, VIDIOCSTUNER, &tuner);
    }

    if ( ioctl(video_fd,VIDIOCGMBUF,&gb_buffers) < 0 )
	{
		printf("capture not support mbuf\n");
		return -1;
	}
	
    win.x = 0;
    win.y = 0;
    win.width = width;
    win.height = height;
    win.chromakey = -1;
    win.flags = 0;
    ioctl(video_fd, VIDIOCSWIN, &win);
    
    if(mmap_done==0)
    {
        mmap_done = 1;
    #ifndef NEW_VERSION
	for(i=0; i<gb_buffers.frames; i++){
		gb_buffers.offsets[i] = mmap(0, gb_buffers.size, PROT_READ|PROT_WRITE, MAP_SHARED, video_fd, 0);
    	        if ((unsigned char*)-1 == gb_buffers.offsets[i]) {
                      	printf("mmap error\n");
	  	  	return -1;
		}
        }
    #else

        video_buf_virt = mmap(0, gb_buffers.size, PROT_READ|PROT_WRITE, MAP_SHARED, video_fd, 0);
        //printf("gb_buffers.size=%d\n", gb_buffers.size);
    #endif

        //iptv_setting(ingrab);
        ioctl(video_fd, 0x5679, &ingrab);            //grab type
        cap_type=yuv_format;
        ioctl(video_fd, 0x5678, &cap_type);
    }

    gb_buf.width = width;
    gb_buf.height = height;
    gb_buf.format = VIDEO_PALETTE_YUV420P;
    gb_buf.frame = 0;    
	return 0;

}


#define IMAGE_COMP		3
#define MAX(x, y) ((x) > (y) ? (x) : (y))
#define MIN(x, y) ((x) < (y) ? (x) : (y))

#define SET_COMP(index,hsamp,vsamp)  \
  (enc_param.rgComponentInfo[index].m_u8HSamplingFrequency = (hsamp), \
   enc_param.rgComponentInfo[index].m_u8VSamplingFrequency = (vsamp))

struct mdcfg {
    unsigned int    exist;
    pid_t           pid;
    unsigned int    md_interval;
    unsigned int    x0_LU;
    unsigned int    y0_LU;
    unsigned int    x0_RD;
    unsigned int    y0_RD;
    
    unsigned int    x1_LU;
    unsigned int    y1_LU;
    unsigned int    x1_RD;
    unsigned int    y1_RD;
    
    unsigned int    x2_LU;
    unsigned int    y2_LU;
    unsigned int    x2_RD;
    unsigned int    y2_RD;

    unsigned int    mv_th0;
    unsigned int    sad_th0;
    unsigned int    dev_th0;
    unsigned int    alarm_idx0;

    unsigned int    mv_th1;
    unsigned int    sad_th1;
    unsigned int    dev_th1;
    unsigned int    alarm_idx1;

    unsigned int    mv_th2;
    unsigned int    sad_th2;
    unsigned int    dev_th2;
    unsigned int    alarm_idx2;
    unsigned int     ver;
    unsigned char   filename[30];
    unsigned int    rotate;
    unsigned int    rotate_idx;
    unsigned int    mdt1;
    unsigned int    mdt2;
    unsigned int    mdt3;
    unsigned int    mdt4;	
    unsigned int    mdt5;
} mdcfg;


struct mdcfg mdval;

void fmj_set_motion_cfg(struct mdcfg *val)
{
    	memcpy(&mdval,val,sizeof(struct mdcfg));
}

#if 0
void motion_detection_init(FJPEG_ENC_PARAM *enc_param)
{
	
	enc_param->range_mb_x0_LU = mdval.x0_LU;
  	enc_param->range_mb_y0_LU = mdval.y0_LU;
  	enc_param->range_mb_x0_RD = mdval.x0_RD;
  	enc_param->range_mb_y0_RD = mdval.y0_RD;
  	enc_param->range_mb_x1_LU = mdval.x1_LU;
  	enc_param->range_mb_y1_LU = mdval.y1_LU;
  	enc_param->range_mb_x1_RD = mdval.x1_RD;
  	enc_param->range_mb_y1_RD = mdval.y1_RD;
  	enc_param->range_mb_x2_LU = mdval.x2_LU;
  	enc_param->range_mb_y2_LU = mdval.y2_LU;
  	enc_param->range_mb_x2_RD = mdval.x2_RD;
  	enc_param->range_mb_y2_RD = mdval.y2_RD;
        enc_param->mdt1 = mdval.mdt1;
	enc_param->mdt2 = mdval.mdt2;
	enc_param->mdt3 = mdval.mdt3;
	enc_param->mdt4 = mdval.mdt4;
	enc_param->mdt5 = mdval.mdt5;
  	//enc_param->MV_th0 = mdval.mv_th0;    
  	//enc_param->sad_th0 = mdval.sad_th0;   
  	enc_param->delta_dev_th0 = mdval.dev_th0;   
                                     
  	//enc_param->MV_th1 = mdval.mv_th1;        
  	//enc_param->sad_th1 = mdval.sad_th1;       
  	enc_param->delta_dev_th1 = mdval.dev_th1;       

  	//enc_param->MV_th2 = mdval.mv_th2;      
  	//enc_param->sad_th2 = mdval.sad_th2;     
  	enc_param->delta_dev_th2 = mdval.dev_th2;     

  	enc_param->md_interval = mdval.md_interval;  	

}
#endif


int mjpeg_init(video_profile *video_setting)
{
         unsigned int    yuv_size=0;  
  	unsigned int    max_h_samp,max_v_samp;
  	int             i;
  	int             YUVsampling;
         unsigned int    image_size[3];
    
    
	fmjpeg_enc_fd=open(FMJPEG_ENCODER_DEV,O_RDWR);
	    	
	if(fmjpeg_enc_fd==0) {
	  	printf("Fail to open %s\n",FMJPEG_ENCODER_DEV);
	         fflush(stdout);
	  	return -1;
	} 

  	enc_param.u32ImageQuality=video_setting->fmjpeg_image_quality; // we set image quality to 90 (0~100)  
  	enc_param.u32RestartInterval=video_setting->u32RestartInterval; // we set restart interval to 5  
 	enc_param.u32ImageWidth=video_setting->width;  // set image width
  	enc_param.u32ImageHeight=video_setting->height; // set image height
  	enc_param.u8NumComponents=IMAGE_COMP; // the input image has 3 components 'YUV'  
         enc_param.u8JPGPIC=video_setting->fmjpeg_jpeg;
         enc_param.u82D=video_setting->YUV_2D;         // 0: for Mp4 1: for JPG 2: for H.264                                                   
         enc_param.u32ImageMotionDetection=mdval.exist; //avctx->fmjpeg_md;
         enc_param.roi_enable = video_setting->roi_enable;
	enc_param.roi_left_x=video_setting->roi_left_x;
         enc_param.roi_left_y=video_setting->roi_left_y;
         enc_param.roi_right_x=video_setting->roi_right_x;
         enc_param.roi_right_y=video_setting->roi_right_y;	 
		 
  	//YUVsampling = 0; // YUV420
  	//YUVsampling = 1; // YUV422
  	//YUVsampling = 2; // YUV211
  	//YUVsampling = 3; // YUV333
  	//YUVsampling = 4; // YUV222
  	//YUVsampling = 5; // YUV111
        YUVsampling=video_setting->fmjpeg_yuv_format;

	if(enc_param.u8NumComponents==1)
            YUVsampling = 5; // if there is only one component, it is gray, so we force it YUV111
    
	switch (YUVsampling) {
		case 0:
		  SET_COMP(0, 2, 2);
		  SET_COMP(1, 1,1);
		  SET_COMP(2, 1,1);
		  break;
		case 1:
		  SET_COMP(0, 4,1);
		  SET_COMP(1, 2,1);
		  SET_COMP(2, 2,1);
		  break;
		case 2:
		  SET_COMP(0, 2,1);
		  SET_COMP(1, 1,1);
		  SET_COMP(2, 1,1);
		  break;
		case 3:
		  SET_COMP(0, 3,1);
		  SET_COMP(1, 3,1);
		  SET_COMP(2, 3,1);
		  break;
		case 4:
		  SET_COMP(0, 2,1);
		  SET_COMP(1, 2,1);
		  SET_COMP(2, 2,1);
		  break;
		case 5:
		  SET_COMP(0, 1,1);
		  SET_COMP(1, 1,1);
		  SET_COMP(2, 1,1);
		  break;
		default:
		  break;
	}  // to set each component's sampling factor (horizontally and vertically)
  	   
  	// get the maximum horizontal sampling factor
  	max_h_samp=MAX(enc_param.rgComponentInfo[0].m_u8HSamplingFrequency,
             	MAX(enc_param.rgComponentInfo[1].m_u8HSamplingFrequency,
                 enc_param.rgComponentInfo[2].m_u8HSamplingFrequency));
  	// get the maximum horizontal sampling factor
  	max_v_samp=MAX(enc_param.rgComponentInfo[0].m_u8VSamplingFrequency,
             	MAX(enc_param.rgComponentInfo[1].m_u8VSamplingFrequency,
                 enc_param.rgComponentInfo[2].m_u8VSamplingFrequency)); 
                 
         
  	// calculate each component size according to its maximum sampling factor
  	// and individual sampling factor
  	for(i=0;i<enc_param.u8NumComponents;i++) {
             image_size[i]=(((enc_param.rgComponentInfo[i].m_u8HSamplingFrequency*enc_param.u32ImageWidth) /max_h_samp) *
                    ((enc_param.rgComponentInfo[i].m_u8VSamplingFrequency*enc_param.u32ImageHeight) /max_v_samp));
             yuv_size += image_size[i];
         }

  	if ( ioctl(fmjpeg_enc_fd,FMJPEG_IOCTL_ENCODE_CREATE,&enc_param) < 0 ) {
		printf("Error to set FMJPEG_IOCTL_ENCODE_CREATE\n");
        		fflush(stdout);
                  return -1;
  	}
  	return 0;
}


int mjpeg_encode(video_profile *video,uint8_t *buf,void *data)
{
        	AVFrame         *pav=(AVFrame *)data;  
  	int             i;

         if ( video->roi_enable ) {
		 enc_param.roi_left_x = video->roi_left_x;
		 enc_param.roi_left_y = video->roi_left_y;
		 enc_param.roi_right_x = video->roi_right_x;
		 enc_param.roi_right_y = video->roi_right_y;
         }
		 	
	for(i=0;i<enc_param.u8NumComponents;i++)
             enc_param.pu8YUVAddr[i]=(unsigned char *)pav->data[i];  // capture YUV virtual addres

	enc_param.pu8BitstreamAddr=buf;
	if ( ioctl(fmjpeg_enc_fd,FMJPEG_IOCTL_ENCODE_ONE,&enc_param) < 0 ) {
	     printf("Error to set FMJPEG_IOCTL_ENCODE_ONE\n"); 
              fflush(stdout);
              return -1;
         }	

	return enc_param.bitstream_size;	
}


int mjpeg_close(video_profile *video_setting)
{
	if ( ioctl(fmjpeg_enc_fd,FMJPEG_IOCTL_ENCODE_DESTROY,&enc_param) < 0) {
             printf("Error to set FMJPEG_IOCTL_ENCODE_DESTROY\n");
             fflush(stdout);
             return -1;
         }
	close (fmjpeg_enc_fd);
	return 0;
}


unsigned int  encode_video(video_profile *video, int index)
{	
    AVFrame             yuv_data;
    unsigned char     *ptr;
    
    if (ioctl(video_fd, VIDIOCMCAPTURE, &gb_buf) < 0) {
        if (errno == EAGAIN)
            fprintf(stderr,"Cannot Sync\n");
        else
            perror("VIDIOCMCAPTURE");
        return 0 ;
    }
    	
    #ifdef NEW_VERSION
    //printf("frame_num = %d gb_buffers.offsets[frame_num] =%d\n", frame_num, gb_buffers.offsets[frame_num]);
    while (ioctl(video_fd, VIDIOCSYNC, &frame_num) < 0 && (errno == EAGAIN || errno == EINTR))printf("error\n");
         ptr = video_buf_virt + gb_buffers.offsets[frame_num];
         //printf("gb_buf.frame=%d\n", gb_buf.frame);
         frame_num = gb_buf.frame;
    #else
    while (ioctl(video_fd, VIDIOCSYNC, &frame_sync) < 0 && (errno == EAGAIN || errno == EINTR));
         ptr = (uint8_t *)frame_sync.mmapAddr;
    #endif

     yuv_data.data[0] = (unsigned char *)(ptr);
     yuv_data.data[1] = (unsigned char *) (ptr + video_setting.ysize);
     yuv_data.data[2] = (unsigned char *) (ptr + video_setting.ysize * 5 /4);
    
    return (unsigned int) mjpeg_encode(video, out_virt_buffer[index], (void *)&yuv_data);	

}
#define ROI_LEFT_X 		0
#define ROI_LEFT_Y 		0
#define ROI_RIGHT_X 		320
#define ROI_RIGHT_Y 		240

int main(int argc, char **argv)
{
    int  i, j;  
    int width, height;	
    unsigned int blength;
    char    dout_name[30];
    FILE    *dout;	

     if(argc<3) {
        		printf("Usage: \n");
        		printf("     jpeg_main -f 0: initization without ROI and MP4 2D YUV\n");
		printf("     jpeg_main -f 1: initization without ROI and Jpg sequencial YUV\n");		
        		printf("     jpeg_main -r 0: initization with ROI and MP4 2D YUV\n");
		printf("     jpeg_main -r 1: initization with ROI and Jpg sequencial YUV\n");	
		return 0;
    }
     if (argc > 2)     {
        // something simple for now

	if (strcmp("-f", argv[1]) == 0)
		video_setting.roi_enable = 0;
	else if (strcmp("-r", argv[1]) == 0)
		video_setting.roi_enable = 1;
	yuv_format = atoi(argv[2]);

    }
	 
    //set the default value
    video_setting.qmax = 31;
    video_setting.qmin = 1;
    video_setting.quant = 0;
    video_setting.bit_rate = 512;
    video_setting.width = 720;
    video_setting.height = 480;
    video_setting.framerate = 5;
    video_setting.frame_rate_base = 1;
    video_setting.gop_size = 30;
    video_setting.fmjpeg_image_quality=40;      //need assign by UI
    video_setting.u32RestartInterval=10;        //need assign by UI
    video_setting.fmjpeg_jpeg=1;        		//0 for motion JPEG, 1 for JPEG
    video_setting.fmjpeg_yuv_format=0;           //0 for YUV420	
    video_setting.YUV_2D = yuv_format;

    video_setting.roi_left_x=ROI_LEFT_X;
    video_setting.roi_left_y=ROI_LEFT_Y;
    video_setting.roi_right_x=ROI_RIGHT_X;
    video_setting.roi_right_y=ROI_RIGHT_Y;

    
    
	
     //Fix the 7137 issue
    if((ingrab==13)||(ingrab==14)||(ingrab==15)) {
    	if(video_setting.width==720)
    		video_setting.width=704;
    }
    
    height = video_setting.height;		
    if(height % 16 != 0)
         height = 16 * ((video_setting.height / 16) + 1 );		
    width = video_setting.width;
    if(width % 16 != 0)
         width = 16 * ((video_setting.width / 16) + 1 );	
    video_setting.ysize = width*height;
    for ( i=0; i<5; i++)
    	out_virt_buffer[i] = (unsigned char *) malloc(sizeof(unsigned char )*video_setting.ysize* 3/2);	
	
    if( v4l_init(video_setting.width, video_setting.height) < 0)
        return -1;

    if(mjpeg_init(&video_setting)<0)
        return -1;

    for(i=0;i<5;i++)  {
	if ( video_setting.roi_enable) {
		video_setting.roi_right_x = ROI_RIGHT_X + i*32;
		video_setting.roi_right_y = ROI_RIGHT_Y + i*32;
		video_setting.roi_left_x = ROI_LEFT_X + i*32;
		video_setting.roi_left_y = ROI_LEFT_Y + i*32;
	}
	
       if ( video_setting.roi_enable) {
	   if ( yuv_format == 0)		
	          sprintf(dout_name,"/tmp/share/mp4yuv_roi%d.jpg",i);
	   else if ( yuv_format == 1)
	   	 sprintf(dout_name,"/tmp/share/jpgyuv_roi%d.jpg",i);
	   else if ( yuv_format == 2)
	   	 sprintf(dout_name,"/tmp/share/h264yuv_roi%d.jpg",i);
        } else {		
            if ( yuv_format == 0)		
	          sprintf(dout_name,"/tmp/share/mp4yuv_img%d.jpg",i);
	   else if ( yuv_format == 1)
	   	 sprintf(dout_name,"/tmp/share/jpgyuv_img%d.jpg",i);
	   else if ( yuv_format == 2)
	   	 sprintf(dout_name,"/tmp/share/h264yuv_img%d.jpg",i);
        	}		
        dout=fopen(dout_name,"wb");
        printf("Use output name %s\n",dout_name);fflush(stdout);        
        blength = encode_video(&video_setting, i );      
        fwrite((void *)out_virt_buffer[i],blength,1,dout); 		
        fclose(dout);
        fflush(dout);		
        memset( out_virt_buffer[i], 0 , sizeof(unsigned char )*video_setting.ysize );		
    }

  	
    if ( mjpeg_close(&video_setting) < 0 )
        printf("mjpeg_close fail\n");		
		
    for (i=0; i<5; i++)			
	 free(out_virt_buffer[i]);	
		
    close(video_fd);
		
    return 0;
}
